#ifndef  SHINY_WORLD_HPP
# define SHINY_WORLD_HPP

#include <Box2D/Box2D.h>
#include "../datatree.hpp"
#include "../texturelist.hpp"
#include "../soundlist.hpp"
#include "../ressourceslist.hpp"
#include "../scriptlist.hpp"
#include "../renderressources.hpp"
#include "applejackobject.hpp"
#include "camera.hpp"
#include "../game.hpp"
#include "applejackscript.hpp"

namespace Shiny
{
  namespace Applejack
  {
    class Fixture;

    class World : public Level, public b2World
    {
    public:
      struct RayCastCollision
      {
	Fixture*     fixture;
	sf::Vector2f point;
	sf::Vector2f normal;
	float        fraction;

	bool operator<(const RayCastCollision&) const;
      };

      typedef std::vector<RayCastCollision> RayCastCollisions;

      class RayCastObserver : public b2RayCastCallback, public Observatory::Signal<RayCastCollisions>
      {
	  friend class World;
	  float32 ReportFixture(b2Fixture *fixture, const b2Vec2 &point, const b2Vec2 &normal, float32 fraction);
      public:
	  enum Behaviour
	  {
	      Closest,  // Find the closest collision
	      Collide,  // Just find if there is a collision
	      All,      // Find all the collisions
	      AllSorted // Find all the collisions and sort them
	  };

	  RayCastObserver(World&, sf::Vector2f point1, sf::Vector2f point2, Behaviour = All);
	  void Trigger(void);
	  void DeleteLater(void) { _world.DelRayCast(this); }

	  static void TriggerIterate(RayCastObserver*);

      private:
	  virtual ~RayCastObserver() {}
	  RayCastCollisions _collisions;
	  Behaviour         _behaviour;
	  World&            _world;
      };

      class CollisionManager : public b2ContactListener
      {
      public:
        void BeginContact(b2Contact* contact);
        void EndContact(b2Contact* contact);

        void PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
        { /* handle pre-solve event */ }

        void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse)
        { /* handle post-solve event */ }
      };

      ~World();

      Widgets::Container*      CreateView(void);

      class Factory
      {
      public:
        static Level*            New(const std::string& levelPath, Data);

      private:
        typedef void (*GenerateFunction)(const std::string&, World*, Data);
        static void              GenerateResources(const std::string& levelPath, World* world, Data level);
        static void              GenerateEntities(const std::string& levelPath, World* world, Data level);
        static void              GenerateScript(const std::string& levelPath, World* world, Data level);
        static void              GenerateLandscape(const std::string& levelPath, World* world, Data level);
        static void              GenerateHud(const std::string& levelPath, World* world, Data level);

        static GenerateFunction  _generateFunctions[];
      };

      const unsigned int       Ratio(void) const                      { return (_ratio);     }
      sf::Vector2f             GetSize(void) const                    { return (_size);      }
      const sf::Texture*       GetLandscape(void) const               { return (_landscape); }
      TextureList&             GetTextures(void) const                { return (*_tex);      }
      AnimationList&           GetAnimations(void) const              { return (*_anims);    }
      SoundList&               GetSounds(void) const                  { return (*_sounds);   }
      const ObjectList&        GetObjects(void) const                 { return (_objects);   }
      Module*                  GetModule(void)                        { return (_script);    }
      void                     SetModule(Module* script)              { _script = script;    }

      void                     Update(void);
      Body*                    GetBody(const std::string&);
      void                     DeleteBody(Body*);
      CollisionManager&        GetCollisionManager(void)              { return (_collisionManager); }

      void                     AddRayCast(RayCastObserver* r) { _raycastObservers.push_back(r);         }
      void                     DelRayCast(RayCastObserver* r) { _toDeleteRaycastObservers.push_back(r); }

    private:
      World(b2AABB, b2Vec2, unsigned int ratio);

      void               DeleteBodies(void);
      void               DeleteRaycastObservers(void);

      unsigned int       _ratio;
      sf::Vector2f       _size;
      TextureList*       _tex;
      AnimationList*     _anims;
      SoundList*         _sounds;
      ObjectList         _objects;
      CollisionManager   _collisionManager;
      Module*            _script;
      const sf::Texture* _landscape;

      std::list<Body*>            _toDeleteBodies;
      std::list<RayCastObserver*> _raycastObservers;
      std::list<RayCastObserver*> _toDeleteRaycastObservers;

      /*
      * TODO: Try to shove this part in a AppleScript Manager
      */
    public:
      LibList&          GetLibList(void) { return (_cppList); }
    private:
      LibList           _cppList;
    };
  }
}

#endif
